#include "processmanager.h"
#include "system.h"
#include <stdio.h>
#include <limits.h>



// Empty constructor
ProcessManager::ProcessManager()
{

}


//Destructor .. nothing for now
ProcessManager::~ProcessManager()
{


}


// Does clean up work when a process exits
// Does not necessarily leave the pid free to reuse again
void ProcessManager::processFinished(int pid, int status)
{
	// Verify some assumptions
	ASSERT(pid >= 0 && pid < pcbVector.size());
	ASSERT(pcbVector[pid] != NULL);
	
	// Delete (and thus deallocate) the address space
	AddrSpace* space = pcbVector[pid]->getThread()->space;
	delete space;
	
	int parentPID = pcbVector[pid]->getParentPID();


	// If the process was parent-less, destroy its entry
	if(parentPID == NO_PARENT)
	{
		delete pcbVector[pid];
		pcbVector[pid] = NULL;
	}
	else // Else, save its exit status for now
	{
		pcbVector[pid]->setExited(true);
		pcbVector[pid]->setExitStatus(status);

		// Check if there is a waiting parent to wake
		ProcessCB *parentPCB = pcbVector[parentPID];
		
		// Check if the parent has since exited
		if(parentPCB == NULL)
		{
			delete pcbVector[pid];
			pcbVector[pid] = NULL;		
		}
		else
		{
			if(parentPCB->getWaiting() && !parentPCB->hasExited())
			{
				// Make sure its waiting on this process
				if(parentPCB->getWaitingOn() == pid);
				{
					IntStatus oldLevel = interrupt->SetLevel(IntOff);
					scheduler->ReadyToRun(parentPCB->getThread());
					(void) interrupt->SetLevel(oldLevel);
				}	
			}
		}		
	}	
}


// Factory method to create a PCB
ProcessCB* ProcessManager::constructPCB(Thread *thread, int parentPID)
{
	int pid = getFreePID();

	ProcessCB *pcb = new ProcessCB(thread, pid, parentPID);
	pcbVector[pid] = pcb;
	return pcb;
}


// Gets the first free pid in the PCB vector, will make room if necessary
int	ProcessManager::getFreePID()
{
	// Find a free PID in the vector
	for(int i=0; i<pcbVector.size(); i++)
	{
		if(pcbVector[i] == NULL)
		{
			return i;
		}
	}

	// If no room is free in the current array, add a free spot to the end and return that
	pcbVector.push_back(NULL);

	return ( pcbVector.size()-1 );
}


// Gets the PCB with the given pid, or NULL if there is no match or out of range
ProcessCB* ProcessManager::getPCB(int pid)
{
	if(pid < pcbVector.size() && pid >= 0)
		return pcbVector[pid];
	else
		return NULL;
}


// Returns the first pid of the first PCB that uses the given thread AND is still running (has not exited)
int ProcessManager::findPIDByThread(Thread *thread)
{
	// Only return valid matches
	if(thread == NULL)
		return -1;

	for(int i=0; i<pcbVector.size(); i++)
	{
		if(pcbVector[i] == NULL)
			continue;
		if(pcbVector[i]->getThread() == thread && !pcbVector[i]->hasExited())
			return i;
	}

	// No match
	return -1;
}

int ProcessManager::waitOn(int pid, int waitOnPID)
{
	// Get the parents PCB
	ProcessCB *pcb = processManager->getPCB(pid);
	if(pcb == NULL)
		return -1;

	// Gets the childs PCB
	ProcessCB *childPCB = processManager->getPCB(waitOnPID);
	if(childPCB == NULL)
		return -1;

	// Check if the child already exited
	if(childPCB->hasExited())
		return childPCB->getExitStatus();	

	// Otherwise, wait for the child to finish
	pcb->setWaiting(true);
	pcb->setWaitingOn(waitOnPID);

	// Interrupts must be off to call thread::sleep
    IntStatus oldLevel = interrupt->SetLevel(IntOff);
	currentThread->Sleep();
    (void) interrupt->SetLevel(oldLevel);

	// Get the exit code from the child process
	ASSERT(childPCB->hasExited());
	return childPCB->getExitStatus();
}

OpenUserFile* ProcessManager::findOpenUserFileByName(char *fileName)
{
	OpenUserFile* openFile;
	for(int i=0; i<pcbVector.size(); i++)
	{
		if(pcbVector[i] != NULL)
		{
			AddrSpace *space = pcbVector[i]->getThread()->space;
			openFile = space->findOpenUserFileByName(fileName);
			if(openFile != NULL)
				return openFile;

		}
	}
	return NULL;
}



void	ProcessManager::handlePageFault(int vaddr)
{

	// CHeck if we don't have to evict a page, e.g. there is room in memory
	// TODO use global version instead - low priority
	MemoryManager *memoryManager = currentThread->space->getMemoryManager();
	int freeFrame = memoryManager->allocatePhysicalPage();
	if(freeFrame != MM_ALLOCATION_FAILED)
	{
		// Found a free frame without having to kick anything out
		currentThread->space->loadPage(vaddr, freeFrame);
		return;
	}
	

	// LRU
	int minLRUPageIndex = -1;
	int minValue = INT_MAX;
	int pidWithLRUPage = -1;
	AddrSpace* spaceWithLRUPage = NULL;

	for(int i=0; i<pcbVector.size(); i++)
	{
		if(pcbVector[i] == NULL)
			continue;
		if(pcbVector[i]->hasExited())
			continue;
		AddrSpace *addrSpace = pcbVector[i]->getThread()->space;
		int lruPageIndex = addrSpace->getLRUPage();
		// Check if the space did not have any pages in memory
		if(lruPageIndex == -1)
			continue;
		int lruValue = addrSpace->getPage(lruPageIndex)->lastUsed;

		if(lruValue < minValue && lruValue != -1)
		{
			minValue = lruValue;
			pidWithLRUPage = pcbVector[i]->getPID();
			spaceWithLRUPage = addrSpace;
			minLRUPageIndex = lruPageIndex;
		}
	}

	ASSERT(pidWithLRUPage != -1);
	ASSERT(spaceWithLRUPage != NULL);

	// Get the location of the page to be evicted
	freeFrame = spaceWithLRUPage->getPage(minLRUPageIndex)->physicalPage;

	// Should have the necessary information to evict the page now
	spaceWithLRUPage->writePage(minLRUPageIndex);

	// Load in the new page
	currentThread->space->loadPage(vaddr, freeFrame);
}



